#-*- coding:utf-8 -*-

import os
import sys
import json
import copy
import re
import shutil

projectDir = os.getcwd()
config =  {
    "deleteComment": {
        "c": 0, 
        "classpath": 0, 
        "cpp": 0, 
        "h": 0, 
        "java": 0, 
        "project": 0, 
        "xml": 0
    }, 
    "exclude": {
        "All": [
          "java_repackage_ports/.*", 
          "gen/.*", 
          "xtend-gen/.*"
        ],
    }, 
    "extract": {
        "All": [
            "src/.*", 
            "libs/.*", 
            "assets/.*", 
            "jni/.*", 
            "xtend-gen/.*",
            "\\.classpath", 
            "\\.project"
        ], 
        "Android": [
            "res/.*", 
            "androidmanifest\\.xml", 
            "proguard\\.cfg", 
            "project\\.properties"
        ]
    }, 
    "lineCommentBeginString": {
        "c": "//", 
        "classpath": "<!--", 
        "cpp": "//", 
        "h": "//", 
        "java": "//", 
        "project": "<!--", 
        "xml": "<!--"
    }, 
    "platformDeduced": {
        "Linux": [
            "Java"
        ], 
        "Windows": [
            "Java"
        ]
    }, 
    "process": [
        ".*\\.java", 
        "\\.classpath", 
        "\\.project"
    ]
}

platforms = []
process=[] #正则表达式表示的相对路径列表, 表示需要进行针对define和platform的预处理
predefined= {} #define过的键-值, 来源有config.repackage和process列表中的文件. 每个文件处理完后, 恢复到config的状态
defined = {}
extract=[] #当前平台需要被提取的文件的相对路径的正则表达式
exclude = [] #当前平台需要被排除的文件的相对路径的正则表达式, 实际提取的是{extract}-{exclude}
handled = False
word = re.compile("[a-zA-Z_][a-zA-Z_0-9]*")
nonspace = re.compile(r"[^\s]+")
defWord = re.compile(r"`[a-zA-Z_][a-zA-Z_0-9]*`")

#process所含文件预处理命令注释格式： 以下用"//"代替所在文件的行注释符, 不同拓展名文件的行注释符在config里声明
# "//#define xx yy " define前面不能空格, yy后面一定要是空格或者行尾
# "//#if[n]def xx [...]" 或 "//#platform xx [...]" 在行尾标注，控制单行
# "//##if[n]def xx [...]" 或 "//##platform xx [...]" 控制下一行直到遇到与之匹配的"//##endif"或"//##endplatform"
# ... 可以是define xx yy,define xx ,define xx, 最后的逗号随意保留, define前面不能有空格 被xx和yy之间要空格, 后面随意空格
# [...]可以省略
# 也可以把块注释作为行注释使用, 这里并不严格检查
# 在行中使用``包围一个token, 将检查defined表, 用已经define的字符串替代. 可以多次使用

def addRecursively(value, newValue):
  if type(value) == type(newValue):
    if isinstance(value, dict):
      for k in newValue.keys():
        if k in value.keys():
          addRecursively(value[k], newValue[k])
        else:
          value[k] = newValue[k]
    elif isinstance(value, (list, set)):
      for val in newValue:
        if isinstance(val, dict):
          if val in value.keys():
            addRecursively(value[val], newValue[val])
          else:
            value.append(val)
        elif not val in value:
          value.append(val)
    else:
      value = newValue
  else:
    print "类型不匹配", value, type(value), newValue, type(newValue)
    return False

def readConfig():
  global config
  
  if os.path.exists("config.repackage") and os.path.isfile("config.repackage"):
      h = open("config.repackage", "r")
  else:
    return False

  newConfig = json.load(h)
  addRecursively(config, newConfig)
  h.close()
  
  return True
    
def updateConfig(thisPlatform):
  global config
  global platforms
  global predefined
  global defined
  global process
  global extract
  global exclude
  
  #try:
    #while True:
      #platforms.pop()
  #except:
    #pass
  
  #try:
    #while True:
      #process.pop()
  #except:
    #pass
  
  #try:
    #while True:
      #extract.pop()
  #except:
    #pass
  
  #try:
    #while True:
      #exclude.pop()
  #except:
    #pass
  platforms = []
  process=[]
  extract=[]
  exclude=[]
  predefined={}
  defined.clear()
  
  platform = thisPlatform
  
  knownPlatforms = []

  platforms = [platform]
  if config["platformDeduced"].has_key(platform):
    for p in config["platformDeduced"][platform]:
      platforms.append(p)
  
  knownPlatforms += platforms
  
  for p in config["platformDeduced"].keys()+config["exclude"].keys()+config["extract"].keys():
    knownPlatforms.append(p)
  for key in config["platformDeduced"].keys():
    for p in config["platformDeduced"][key]:
      knownPlatforms.append(p)
  knownPlatforms = set(knownPlatforms)
  
  predefined = copy.deepcopy(config["defined"])
  predefined["platform"] = platform
  for p in platforms:
    if not predefined.has_key(p):
      predefined[p] = ""

  defined = copy.deepcopy(predefined)
  process = iniFileList(config["process"])

  for p in platforms:
    try:
      exclude += iniFileList(config["exclude"][p])
    except:
      print "WANING: exclude of platform", p, "not defined"
  for p in platforms:
    try:
      extract += iniFileList(config["extract"][p])
    except:
      print "WANING: extract of platform", p, "not defined"
  try:
    exclude += iniFileList(config["exclude"]["All"])
  except:
    print "WANING: exclude of All not defined"
  try:
    extract += iniFileList(config["extract"]["All"])
  except:
    print "WANING: extract of All not defined"
  
  for p in knownPlatforms:
    if not p in platforms:
      try:
        p = p.lower()
      except:
        pass
      
      exclude.append(toReg(".*port_"+p+".*"))   #最大程度地智能排除别的平台的文件 只要路径或名称含有已知但并非本工程的平台的名称的小写, 而且有前缀"port_", 则排除
      
  return True

def iniFileList(list):
  l = []
  for i in list:
    s = expandFileName(i)
    re = toReg(s)
    l.append(re)
  return l

def toReg(i):
  return re.compile(i, re.I)

def dumpJson(path):
  h = open(path, "w")
  json.dump(config, h ,sort_keys=True, indent=2)
  h.close()

def iniConfigFile():
  dumpJson("config.repackage")

def expandFileName(name):
  name = trimFileName(projectDir)+ "/" + name.lower()
  return name

def trimFileName(file):
  s = file.lower()
  s = file.replace("\\", "/")
  return s

def trimFileNames(list):
  l = []
  for i in list:
    l.append(trimFileName(i))
  return l

def processFile(fileName):
  global config
  global platforms
  global predefined
  global defined
  global process
  global extract
  global exclude
  global handled

  def checkBlock(lineparts, blockValid, wd):
    global handled
    if lineparts[1].startswith(wd):
      token = word.findall(lineparts[1][len(wd) + 1:])[0]
      if wd == "platform":
        blockValid.append(token in platforms)
      elif wd == "ifndef":
        blockValid.append(not token in defined.keys())
      else:
        blockValid.append(token in defined.keys())
        
      handled |= True
    else:
      handled |= False

  def checkLine(lineparts, wd):
    global handled
    if lineparts[1].startswith(wd):
      token = word.findall(lineparts[1][len(wd) + 1:])[0]
      handled = True
      if wd == "platform":
        return token in platforms
      elif wd == "ifndef":
        return not token in defined.keys()
      else:
        return token in defined.keys()
    else:
      return False

  def info(lnum, fileName, inf):
    print "info in", fileName, "line", lnum, inf

  def addDef(inst, defined, lnum, fileName):
    tmp = nonspace.findall(inst)
    if len(tmp) >= 2:
      if defined.has_key(tmp[1]):
        info (lnum, fileName, "redefine: " +  tmp[1])
    try:
      defined[tmp[1]] = tmp[2]
    except:
      try:
        defined[tmp[1]] = ""
      except:
        info(lnum, fileName, "define xx yy")

  def addDefs(lineparts, defined, lnum, fileName):
    if "[" in lineparts[1] and "]" in lineparts[1]:
      instructions = lineparts[1][lineparts[1].find("[") + 1: lineparts[1].find("]")]
      for inst in instructions.split(","):
        # 处理define
        if "define" in inst:
          addDef(inst, defined, lnum, fileName)
  
  defined = copy.deepcopy(predefined)
  h = open(fileName.decode('utf-8').encode('gbk'), "r")
  cms = config["lineCommentBeginString"][getExt(fileName)]
  deleteComment = config["deleteComment"][getExt(fileName)]
  lines = h.readlines()
  trimedLines = []
  blockValid = []
  lnum = 0
  for line in lines:
    lnum += 1
    handled = False #表示有可用处理方案
    defValid = False #表示声明正确, 后面的define语句应该被执行
    
    if (cms+"\\\\") in line:  #开头就注释而且后面紧跟两个反斜线, 意思是防止编辑器报错, 实际上应该有这一行. 把注释符和反斜线去掉
      line = line.replace(cms+"\\\\", "",1)
	
    if blockValid:
      #blockValid非空, 在块控制内
      if cms + "##else" in line:
        blockValid[-1] = not blockValid[-1]
      elif cms + "##end" in line:
        if blockValid[-1]:
          if deleteComment:
            trimedLines.append(line.split(cms + "##end")[0])
          else:
            trimedLines.append(line)
        blockValid.pop()
        continue  #跳过进一步处理
      elif not blockValid[-1]:
        continue

    #不论在块内块外, 行处理逻辑相同
    lineparts = line.split(cms)
    line = lineparts[0]
    defParts = line.split("`")
    if len(defParts) % 2 == 0:
      info(lnum, fileName, "define not closed")
    else:
      i = 0
      for i in range(len(defParts) / 2):
        try:
          defParts[2 * i + 1] = defined[defParts[2 * i + 1]]
        except:
          defParts[2 * i + 1] = "`" + defParts[2 * i + 1] + "`"
    line = "".join(defParts)
    lineparts[0] = line
    line = cms.join(lineparts)

    if cms + "##" in line:
      #块控制开始
      lineparts = line.split(cms + "##")
      checkBlock(lineparts, blockValid, "ifdef")
      checkBlock(lineparts, blockValid, "ifndef")
      checkBlock(lineparts, blockValid, "platform")
      defValid = blockValid[-1]
    elif cms + "#define" in line:
      lineparts = line.split(cms + "#")
      #控制单行
      addDef(lineparts[1], defined, lnum, fileName)
    elif cms + "#" in line:
      lineparts = line.split(cms + "#")
      defValid |= checkLine(lineparts, "ifdef")
      defValid |= checkLine(lineparts, "ifndef")
      defValid |= checkLine(lineparts, "platform")
    else:
      lineparts = [line]

    if handled and defValid:
      addDefs(lineparts, defined, lnum, fileName)

    if not handled or defValid:
      if deleteComment:
        trimedLines.append(lineparts[0])
      else:
        trimedLines.append(line)

  return trimedLines

def getExt(fileName):
  s = ""
  try:
    s = fileName.split(".")[-1]
  except:
    s = ""
  return s

def work(root, exroot):
  root = trimFileName(root)
  exroot = trimFileName(exroot)

  if os.path.exists(exroot):
    filenames=os.listdir(exroot)
    if filenames:
      if raw_input(exroot + " not empty! go ahead anyway?[y/n]") != "y":
        return
      else:
        for f in filenames:
          f = (exroot + "/" + f).encode('gbk')
          if os.path.isfile(f):
            os.remove(f)
            print f+" removed!"
          elif os.path.isdir(f):
            shutil.rmtree(f,True)
            print "dir "+f+" removed!"

  for dirpath, dirnames, filenames in os.walk(root):
    dirpath = trimFileName(dirpath).decode('gbk').encode('utf-8')
      
    exDir = dirpath.replace(root, exroot)
    for filename in filenames:
      filename = filename.decode('gbk').encode('utf-8')
      filename = dirpath + "/" + filename
      exFile = filename.replace(root, exroot)
      
      excluded = False      
      for exc in exclude:
        if exc.match(filename):
          excluded = True
          print "file", filename, "ignored"
      if excluded:
        continue
      
      processed = False
      for m in process:
        if m.match(filename):
          if not os.path.exists(exDir):
            os.makedirs(exDir)
          h = open(exFile, "w")
          h.writelines(processFile(filename))
          h.close()
          processed = True
          print "processed", filename
          break
      if processed:
        continue

      for ex in extract:
        if ex.match(filename):
          if not os.path.exists(exDir):
            os.makedirs(exDir)
          shutil.copyfile(filename, filename.replace(root, exroot))
          print "extracted", filename
          break

if __name__ == "__main__":
  default_encoding = 'utf-8'
  if sys.getdefaultencoding() != default_encoding:
    reload(sys)
    sys.setdefaultencoding(default_encoding)
     
  if readConfig():
    pass
  else:
    con = raw_input (u"config.repackage 不存在, 是否要以默认设置继续? [y/n]")
    if not con.startswith("y"):
      iniConfigFile()
      print u"已取消操作. 请编辑config.repackage后继续"
      exit()

  print u"***********当前设置已经dump到config.dump"
  dumpJson("config.dump")
  
  for p in config["platformInTurn"]:
    updateConfig(p)
    work(os.getcwd(), os.getcwd() + "/java_repackage_ports/" + p)
  print "done"